﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using GraphSharp;
using QuickGraph;
using snat.model;
using snat.algorithms.network;

namespace gui {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        private bool debug = true;
        private bool useTestGraph = false;
        private int testGraphSize = 100;

        private bool nodeFiltersChanged = false;
        private bool edgeFiltersChanged = false;

        private List<Edge> viewEdges = new List<Edge>();
        private List<BaseNode> viewNodes = new List<BaseNode>();

        private SoftMutableBidirectionalGraph<object, IEdge<object>> viewGraph = new SoftMutableBidirectionalGraph<object, IEdge<object>>();
        public SoftMutableBidirectionalGraph<object, IEdge<object>> ViewGraph {
            get {
                return viewGraph;
            }
        }

        public MainWindow() {

            if (useTestGraph) {
                runWithTestGraph();
                InitializeComponent();
                return;
            }
            
            runWithLiveData();

            ConnectionViewModel vm = new ConnectionViewModel();
            DataContext = vm;

            InitializeComponent();
        }

        private void runWithTestGraph() {

            if(debug) Console.WriteLine("GUI :: Replacing SnatGraph with a test graph of size " + testGraphSize);
            string[] vertices = new string[testGraphSize];
            for (int i = 0; i < testGraphSize; i++) {
                vertices[i] = i.ToString();
            }

            List<Edge<object>> edges = new List<Edge<object>>();
            for (int i = 1; i < testGraphSize / 10; i++) {
                edges.Add(new Edge<object>(vertices[i * 0], vertices[i * 1]));
                edges.Add(new Edge<object>(vertices[i * 1], vertices[i * 2]));
                edges.Add(new Edge<object>(vertices[i * 2], vertices[i * 3]));
                edges.Add(new Edge<object>(vertices[i * 9], vertices[i * 8]));
                edges.Add(new Edge<object>(vertices[i * 4], vertices[i * 5]));
                edges.Add(new Edge<object>(vertices[i * 5], vertices[i * 5]));
                edges.Add(new Edge<object>(vertices[i * 7], vertices[i * 2]));
            }

            DateTime pre = DateTime.Now;
            viewGraph.AddVertexRange(vertices);
            if (debug) Console.WriteLine("GUI :: Adding Vertex Range took " + ((TimeSpan)(DateTime.Now - pre)).TotalMilliseconds + "ms");

            pre = DateTime.Now;
            viewGraph.AddEdgeRange(edges);
            if (debug) Console.WriteLine("GUI :: Adding Edge Range took " + ((TimeSpan)(DateTime.Now - pre)).TotalMilliseconds + "ms");

        }

        private void runWithLiveData() {
            NetworkModel.Instance.Init();

            foreach (BaseNode n in NetworkModel.Instance.Nodes.Values) {
                if (debug) Console.WriteLine("Adding node {0}...", n.ID);
                viewGraph.AddVertex(n);
            }
            if (debug) Console.WriteLine("nodes: " + viewGraph.VertexCount);
            foreach (Edge e in NetworkModel.Instance.Edges) {
                try {
                    if (debug) Console.Write("Adding edge {0}->{1}...", e.From.ID, e.To.ID);
                    viewGraph.AddEdge(e);
                    if (debug) Console.WriteLine("done.");
                }
                catch (Exception ex) {
                    if (debug) Console.WriteLine("failed. {0}", ex.Message);
                }
            }
        }

        
        private void ApplyFilters(object sender, RoutedEventArgs e) {

            if (!useTestGraph) {
                if (nodeFiltersChanged) {


                }
                if (edgeFiltersChanged) {
                    // Hide all edges (for this state) where edge is not contained in the viewEdges list, then,
                    // Unhide all edges (from viewEdges) where the edge is currently hidden in our graph model
                    ViewGraph.HideEdges(ViewGraph.Edges.Where(edge => !viewEdges.Contains(edge)));
                    ViewGraph.UnhideEdges(viewEdges.Where(edge => ViewGraph.HiddenEdges.Contains(edge)));
                    edgeFiltersChanged = false;
                }
            }
            else {
                foreach (Edge edge in ViewGraph.Edges) Console.WriteLine("{0} -> {1}", edge.Source, edge.Target);
                //graphLayout.Relayout(); // Perform layout shuffle
            }
        }

        private void UpdateEdgeFilterList(object sender, RoutedPropertyChangedEventArgs<double> e) {

            switch(EdgeFilter.SelectedValue.ToString()) {
                case "Out Degree": UpdateEdgeFilterListOutDegree(); break;
                case "In Degree": UpdateEdgeFilterListInDegree(); break;
                case "Total Degree": UpdateEdgeFilterListMessages(); break;
                default: Console.WriteLine("GUI :: Filter not yet implemented"); break;
            }
        }

        private void UpdateEdgeFilterListOutDegree() {
            if (!Statistics.LargestDegreeOut.HasValue) return; // catch nullable largestDegreeOut

            double? degreeThreshold = (Statistics.LargestDegreeOut * EdgeSliderXAML.Value) + 1;
            viewEdges.Clear();
            Dictionary<BaseNode, Edge> outEdges = new Dictionary<BaseNode, Edge>();
            foreach (KeyValuePair<int, BaseNode> node in NetworkModel.Instance.Nodes) {
                if (node.Value.OutEdgeCount > degreeThreshold) {
                    outEdges.Clear();
                    foreach (Edge edge in node.Value.OutEdges) outEdges.Add((BaseNode)(edge.Target), edge);
                    foreach (KeyValuePair<BaseNode,Edge> n in outEdges) viewEdges.Add(n.Value);
                }
            }
            edgeFiltersChanged = true;

            if (debug) Console.WriteLine("Updating Edge Filters list with new out degree threshold of: {0}", degreeThreshold);
            if (debug) Console.WriteLine("Edges in Graph: {0} Edges to Display: {1}\n", ViewGraph.Edges.Count(), viewEdges.Count);
        }

        private void UpdateEdgeFilterListInDegree() {
            if (!Statistics.LargestDegreeIn.HasValue) return; // catch nullable largestDegreeOut

            double? degreeThreshold = (Statistics.LargestDegreeIn * EdgeSliderXAML.Value) + 1;
            viewEdges.Clear();
            Dictionary<BaseNode, Edge> inEdges = new Dictionary<BaseNode, Edge>();
            foreach (KeyValuePair<int, BaseNode> node in NetworkModel.Instance.Nodes) {
                if (node.Value.InEdgeCount > degreeThreshold) {
                    inEdges.Clear();
                    foreach (Edge edge in node.Value.InEdges) inEdges.Add((BaseNode)(edge.Target), edge);
                    foreach (KeyValuePair<BaseNode, Edge> n in inEdges) viewEdges.Add(n.Value);
                }
            }
            edgeFiltersChanged = true;

            if (debug) Console.WriteLine("Updating Edge Filters list with new in degree threshold of: {0}", degreeThreshold);
            if (debug) Console.WriteLine("Edges in Graph: {0} Edges to Display: {1}\n", ViewGraph.Edges.Count(), viewEdges.Count);
        }

        private void UpdateEdgeFilterListMessages() {
            double maxMessages = (((BaseNode)Statistics.NodeWithMostConnections[0]).MessagesSent + ((BaseNode)Statistics.NodeWithMostConnections[0]).MessagesReceived);
            double degreeThreshold = (maxMessages * EdgeSliderXAML.Value) + 1;
            viewEdges.Clear();

            double messages;
            Dictionary<BaseNode, Edge> edgesToDisplay = new Dictionary<BaseNode, Edge>();
            foreach (KeyValuePair<int, BaseNode> node in NetworkModel.Instance.Nodes) {
                messages = node.Value.MessagesReceived + node.Value.MessagesSent;
                if (messages > degreeThreshold) {
                    edgesToDisplay.Clear();
                    foreach (Edge edge in node.Value.InEdges) edgesToDisplay.Add((BaseNode)(edge.Target), edge);
                    foreach (Edge edge in node.Value.OutEdges) edgesToDisplay.Add((BaseNode)(edge.Target), edge);
                    foreach (KeyValuePair<BaseNode, Edge> n in edgesToDisplay) viewEdges.Add(n.Value);
                }
            }
            edgeFiltersChanged = true;

            if (debug) Console.WriteLine("Updating Edge Filters list with new message threshold of: {0}", degreeThreshold);
            if (debug) Console.WriteLine("Edges in Graph: {0} Edges to Display: {1}\n", ViewGraph.Edges.Count(), viewEdges.Count);
        }

        private void AddFilter(object sender, RoutedEventArgs e) {

        }

        private void RemoveFilter(object sender, RoutedEventArgs e) {

        }

        private int currentMessageCount = 0;
        public int MaxMessages() {
            if (!useTestGraph) return NetworkModel.Instance.Edges.Count;
            else return 0;
        }
        public int NextMessageIndex() {
            if (!useTestGraph) return currentMessageCount + 1;
            else return 1;
        }

        private void PlayAlgorithm(object sender, RoutedEventArgs e) {
            if (!useTestGraph) {
                if(debug) Console.WriteLine("GUI :: Playing algorithm - adding all edges");

                NetworkModel.Instance.Play();
                viewGraph.AddEdgeRange(NetworkModel.Instance.Edges);
                currentMessageCount = MaxMessages();
            }
            else {
                DisplayDialog("You are currently running with a static model and data source. Therefore there is nothing to play.");
            }
        }

        private void PauseAlgorithm(object sender, RoutedEventArgs e) {
            //if (debug) Console.WriteLine("GUI :: Pausing algorithm");
            //NetworkModel.Instance.Pause(); // not yet implemented

            Console.WriteLine("<< EDGES IN GRAPH: >>");
            foreach (Edge edge in ViewGraph.Edges) Console.WriteLine("{0} -> {1}", edge.Source.ToString(), edge.Target.ToString());
            Console.WriteLine("<< END EDGES IN GRAPH >>\n");

            Console.WriteLine("<< HIDDEN EDGES IN GRAPH: >>");
            foreach (Edge edge in ViewGraph.HiddenEdges) Console.WriteLine("{0} -> {1}", edge.Source.ToString(), edge.Target.ToString());
            Console.WriteLine("<< END HIDDEN EDGES IN GRAPH >>\n");

        }

        private void StepAlgorithm(object sender, RoutedEventArgs e) {
            if (!useTestGraph) {
                if (debug) Console.WriteLine("GUI :: Stepping algorithm - adding next set of edges");
                NetworkModel.Instance.Step(1);
                viewGraph.AddEdge(NetworkModel.Instance.Edges.Last());
                currentMessageCount++;
            }
            else {
                DisplayDialog("You are currently running with a static model and data source. Therefore there is nothing to step to.");
            }
        }

        private void JumpByAlgorithm(object sender, RoutedEventArgs e) {
            if (!useTestGraph) {
                if (debug) Console.WriteLine("GUI :: Jumping By {0} steps.", (int)JumpIndex.Value);
                for (int i = 0; i < (int)JumpIndex.Value; i++) {
                    NetworkModel.Instance.Step((int)1);
                    viewGraph.AddEdge(NetworkModel.Instance.Edges.Last());
                    currentMessageCount += (int)JumpIndex.Value;
                }
            }
            else {
                DisplayDialog("You are currently running with a static model and data source. Therefore there is nothing to step to.");
            }
        }

        public void DisplayDialog(string message) {
            string sCaption = "SNAT System Message";
            MessageBoxButton btnMessageBox = MessageBoxButton.OK;
            MessageBoxImage icnMessageBox = MessageBoxImage.Warning;
            string sMessageBoxText = "SNAT System Message :: " + message;
            MessageBoxResult rsltMessageBox = MessageBox.Show(sMessageBoxText, sCaption, btnMessageBox, icnMessageBox);
        }

        public void ExceptionBox(string message) {
            string sCaption = "SNAT Exception";
            MessageBoxButton btnMessageBox = MessageBoxButton.OK;
            MessageBoxImage icnMessageBox = MessageBoxImage.Warning;
            string sMessageBoxText = "SNAT Program Exception :: " + message;
            MessageBoxResult rsltMessageBox = MessageBox.Show(sMessageBoxText, sCaption, btnMessageBox, icnMessageBox);
            if (rsltMessageBox == MessageBoxResult.OK) Application.Current.Shutdown();
        }

    }

    // -------------------------------
    // Custom classes
    // -------------------------------

    // Dropdown filters menu
    public class EdgeFilterType {
        public string Type { get; set; }
        public EdgeFilterType(string type) {
            Type = type;
        }
    }

   

    // Build bound View Model components
    public class ConnectionViewModel : INotifyPropertyChanged {
        public ConnectionViewModel() {

            // Build Combo Box
            IList<EdgeFilterType> list = new List<EdgeFilterType>();
            list.Add(new EdgeFilterType("Out Degree"));
            list.Add(new EdgeFilterType("In Degree"));
            list.Add(new EdgeFilterType("Total Degree"));
            list.Add(new EdgeFilterType("Custom Influence Filter"));
            edgeFilterTypeList = new CollectionView(list);
        }


        // Edge Filter Type List Combo Box
        private readonly CollectionView edgeFilterTypeList;
        public string edgeFilter;
        public CollectionView EdgeFilterTypeList {
            get { return edgeFilterTypeList; }
        }
        public string EdgeFilter {
            get { return edgeFilter; }
            set {
                if (edgeFilter == value) return;
                edgeFilter = value;
                OnPropertyChanged("EdgeFilter");
            }
        }


        private void OnPropertyChanged(string propertyName) {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}